﻿using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.IO;
using System.Text;

namespace ExcelToDataTool
{
    class Program
    {
        #region 路径变量
        //需要转化的excel 表存放路径
        static string m_ExcelDir;
        //生成后的 data 表存放路径
        static string m_DataDir;
        //生成的 data 实体类（c#代码） 存放路径
        static string m_DataEntityDir;
        //生成的 data 管理类（c#代码） 存放路径
        static string m_DataDBDir;
        #endregion

        //excel2003及以前版本
        static string excelExtension2003 = ".xls";
        //excel2007及以后版本
        static string excelExtension2007 = ".xlsx";

        static StringBuilder builder;

        /// <summary>
        /// 入口函数
        /// </summary>
        /// <param name="args">bat 传的参数</param>
        static void Main(string[] args)
        {
            //m_ExcelDir = @"F:\ExcelToDataTest\Excel";
            #region 初始化参数

            if (args == null || args.Length < 4)
            {
                Console.WriteLine("路径参数个数不对，请检查 传入的参数");
                return;
            }

            m_ExcelDir = args[0];
            m_DataDir = args[1];
            m_DataEntityDir = args[2];
            m_DataDBDir = args[3];

            builder = new StringBuilder();

            if (!Directory.Exists(m_ExcelDir))
            {
                Console.WriteLine("找不到 excel 表存放路径:" + m_ExcelDir);
                return;
            }

            //这里 也都判断一下，不代码生成对应的路径，怕有些人不知道 最后生成在哪里了
            if (!Directory.Exists(m_DataDir))
            {
                Console.WriteLine("找不到 生成后的data 表存放路径:" + m_DataDir);
                return;
            }
            if (!Directory.Exists(m_DataEntityDir))
            {
                Console.WriteLine("找不到 data实体类（c#代码）表存放路径:" + m_DataEntityDir);
                return;
            }
            if (!Directory.Exists(m_DataDBDir))
            {
                Console.WriteLine("找不到 data管理类（c#代码） 表存放路径:" + m_DataDBDir);
                return;
            }

            #endregion

            //寻找所有 符合的excel 路径
            FindAllExcelPath();
        }

        /// <summary>
        /// 查找 excel 下所有符合的路径
        /// </summary>
        static void FindAllExcelPath()
        {
            //文件夹下所有的 文件路径
            string[] excelPathArr = Directory.GetFiles(m_ExcelDir);

            for (int i = 0; i < excelPathArr.Length; i++)
            {
                FileInfo fileInfo = new FileInfo(excelPathArr[i]);

                if (!fileInfo.Extension.Equals(excelExtension2003) && !fileInfo.Extension.Equals(excelExtension2007))
                    continue;
                Console.WriteLine("excel 表存放路径:" + fileInfo.FullName);

                //读取excel
                ReadExcel(fileInfo);
            }
        }

        /// <summary>
        ///读取excel 表 方法
        /// </summary>
        /// <param name="excelPath"> excel 表路径</param>
        static void ReadExcel(FileInfo fileInfo)
        {
            //获取 工作簿
            IWorkbook workbook = null;
            using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
            {
                // 2003版本  
                if (fileInfo.Extension.Equals(excelExtension2003))
                    workbook = new HSSFWorkbook(fileStream);
                // 2007版本  
                else if (fileInfo.Extension.Equals(excelExtension2007))
                    workbook = new XSSFWorkbook(fileStream);
            }

            //获取 工作表
            ISheet sheet = workbook.GetSheetAt(0);
            //获取 工作表名称
            string sheetName = sheet.SheetName;

            // 工作表的 行
            IRow row;

            //内存流 对象
            MMO_MemoryStream ms = new MMO_MemoryStream();
            //错误 内容路径
            string errorTitle = fileInfo.FullName + "中：" + sheetName;

            //这个是最大行数 -1，所以这里只需要加 1 才是真正的行数
            int rowCount = sheet.LastRowNum + 1;

            //先把 有多少行数据 写到 流中 
            ms.WriteInt(rowCount - 3);

            //获取多少列
            short column = sheet.GetRow(0).LastCellNum;
            //生成 c# 脚本时候  使用
            string[,] scriptProperty = new string[column,3];

            for (int i = 0; i < rowCount; i++)
            {

                row = sheet.GetRow(i);
                // row.LastCellNum 这一行中的列数数
                for (int j = 0; j < row.LastCellNum; j++)
                {
                    //GetCell() 获取当前行第几列
                    string cellValue = row.GetCell(j).ToString();

                    //我们规定前三行表示 字段名，描述，类型
                    if (i <= 2)
                        scriptProperty[j, i] = cellValue;
                    //第四行开始  才是真正的数据
                    else
                    {
                        string fieldType = sheet.GetRow(2).GetCell(j).ToString();
                        WriteMemoryStream(ms, fieldType, cellValue, errorTitle);
                    }
                }
            }

            //创建 data 表
            CreateData(sheetName, ms.ToArray());

            //脚本命名规则，让头一个字母大写
            string scriptName = sheetName.Substring(0, 1).ToUpper() + sheetName.Substring(1);

            //创建 表格对应的 c# 实体类
            CreateScriptEntity(scriptProperty, scriptName);

            //创建 表格对应的 c# 管理类
            CreateScriptDBMgr(scriptName, sheetName);

            ms.Close();
            workbook.Close();
        }

        /// <summary>
        /// 把读出的excel 中每个字段数据，转化成对应类型，写入到内存流中
        /// </summary>
        /// <param name="stream">内存流对象</param>
        /// <param name="fieldType">字段类型</param>
        /// <param name="value">字段对应的内容</param>
        static void WriteMemoryStream(MMO_MemoryStream stream, string fieldType, string value, string filePath)
        {
            try
            {
                switch (fieldType.ToLower())
                {
                    case "bool":
                        stream.WriteBool(bool.Parse(value));
                        break;
                    case "ushort":
                        stream.WriteUShort(ushort.Parse(value));
                        break;
                    case "short":
                        stream.WriteShort(short.Parse(value));
                        break;
                    case "uint":
                        stream.WriteUInt(uint.Parse(value));
                        break;
                    case "int":
                        stream.WriteInt(int.Parse(value));
                        break;
                    case "ulong":
                        stream.WriteULong(ulong.Parse(value));
                        break;
                    case "long":
                        stream.WriteLong(long.Parse(value));
                        break;
                    case "float":
                        stream.WriteFloat(float.Parse(value));
                        break;
                    case "double":
                        stream.WriteDouble(double.Parse(value));
                        break;
                    case "string":
                        stream.WriteUTF8String(value);
                        break;
                    default:
                        Console.WriteLine(string.Format("文件{0}中有错误类型{1}", filePath, fieldType));
                        return;
                }
            }
            catch (Exception)
            {
                throw new Exception(string.Format("请检查{0}中 字段内容:{1} 是否和 字段类型:{2} 相符合", filePath, value, fieldType));
            }
        }

        /// <summary>
        /// 创建 data 表
        /// </summary>
        /// <param name="dataName"></param>
        /// <param name="buffer"></param>
        static void CreateData(string dataName, byte[] buffer)
        {
            //data 文件路径
            string dataFilePath = string.Format("{0}{1}.data", m_DataDir, dataName);
            
            //写入文件
            FileStream fs = new FileStream(dataFilePath, FileMode.Create);
            fs.Write(buffer, 0, buffer.Length);
            fs.Close();

            Console.WriteLine("导出成功:" + dataFilePath);
        }

        /// <summary>
        /// 创建 表格对应的 c# 实体类
        /// </summary>
        /// <param name="scriptProperty"></param>
        static void CreateScriptEntity(string[,] scriptProperty,string scriptName)
        {
            //清空 builder
            builder.Remove(0, builder.Length);

            builder.AppendLine("/// =======================");
            builder.AppendLine("/// 这是 自动生成的  脚本  请勿修改");
            builder.AppendFormat("/// 时间：{0} \r\n", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
            builder.AppendLine("/// ======================= \r\n");
            builder.AppendFormat("public class {0}DataEntity : DataEntityBase \r\n", scriptName);
            builder.AppendLine("{");

            //这里写循环体  写出拥有的属性
            for (int i = 0; i < scriptProperty.Rank + 1; i++)
            {
                //第一个 id 已经在基类中定义好了
                if (i == 0)
                    continue;

                builder.AppendLine("    ///<summary>");
                builder.AppendLine("    /// " + scriptProperty[i, 1]);
                builder.AppendLine("    ///<summary>");
                builder.AppendFormat("    public {0} {1} {2}", scriptProperty[i, 2],scriptProperty[i, 0],"{get;} \r\n");
                builder.AppendLine("    ");
            }

            builder.AppendLine("    #region 构造函数");
            builder.AppendFormat("    public {0}DataEntity() : base() {1}", scriptName, "{ } \r\n");
            builder.AppendFormat("    public {0}DataEntity(MMO_MemoryStream ms) : base(ms) \r\n", scriptName);
            builder.AppendLine("    {");

            //这里写循环体 解析赌赢的属性
            for (int i = 0; i < scriptProperty.Rank + 1; i++)
            {
                builder.AppendFormat("        {0} = ms.{1}; \r\n", scriptProperty[i, 0], GetReadType(scriptProperty[i, 2]));
            }

            builder.AppendLine("    }");
            builder.AppendLine("    #endregion");
            builder.AppendLine("}");

            using (FileStream fs = new FileStream(m_DataEntityDir + scriptName + "DataEntity.cs", FileMode.Create))
            {
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    sw.Write(builder.ToString());
                }
            }
        }

        /// <summary>
        /// 根据 excel 中字段类型，获取 读取流 类型
        /// </summary>
        /// <returns></returns>
        static string GetReadType(string fieldType)
        {
            string readType = null;
            try
            {
                switch (fieldType.ToLower())
                {
                    case "bool":
                        return "ReadBool()";
                    case "ushort":
                        return "ReadUShort()";
                    case "short":
                        return "ReadShort()";
                    case "uint":
                        return "ReadUInt()";
                    case "int":
                        return "ReadInt()";
                    case "ulong":
                        return "ReadULong()";
                    case "long":
                        return "ReadLong()";
                    case "float":
                        return "ReadFloat()";
                    case "double":
                        return "ReadDouble()";
                    case "string":
                        return "ReadUTF8String()";
                    default:
                        Console.WriteLine(string.Format("文件中有类型找不到 对应的解析方法{0}", fieldType));
                        break;
                }
            }
            catch (Exception)
            {
                throw new Exception(string.Format("请检查字段类型:{0}是否正确", fieldType));
            }

            return readType;
        }

        /// <summary>
        /// 创建 表格对应的 c# 管理类
        /// </summary>
        /// <param name="scriptProperty"></param>
        static void CreateScriptDBMgr(string scriptName,string dataName)
        {
            //清空 builder
            builder.Remove(0, builder.Length);

            builder.AppendLine("/// =======================");
            builder.AppendLine("/// 这是 自动生成的  脚本  请勿修改");
            builder.AppendFormat("/// 时间：{0} \r\n", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"));
            builder.AppendLine("/// ======================= \r\n");
            builder.AppendFormat("public class {0}DataDBMgr : DataDBMgrBase<{0}DataEntity, {0}DataDBMgr> \r\n", scriptName);
            builder.AppendLine("{");
            builder.AppendLine("    protected override string DataName { get { return \""+ dataName + ".data\"; } }");

            builder.AppendLine("    ");

            builder.AppendFormat("    protected override {0}DataEntity GetEntity(MMO_MemoryStream ms) \r\n", scriptName);
            builder.AppendLine("    {");
            builder.AppendFormat("        {0}DataEntity entity = new {0}DataEntity(ms); \r\n", scriptName);
            builder.AppendLine("        return entity;");
            builder.AppendLine("    }");
            builder.AppendLine("}");

            using (FileStream fs = new FileStream(m_DataDBDir + scriptName + "DataDBMgr.cs", FileMode.Create))
            {
                using (StreamWriter sw = new StreamWriter(fs))
                {
                    sw.Write(builder.ToString());
                }
            }
        }
    }
}
